package gulimall.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import gulimall.constant.CartConstant;
import gulimall.feign.ProductFeignService;
import gulimall.interceptor.CartInterceptor;
import gulimall.pojo.to.UserInfoTo;
import gulimall.pojo.vo.Cart;
import gulimall.pojo.vo.CartItemVO;
import gulimall.pojo.vo.SkuInfoVO;
import gulimall.pojo.vo.SkuSaleAttrValueVO;
import gulimall.service.CartService;
import gulimall.utils.R;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author HLF
 * @date 2022/8/12 10:58
 */
@Service
public class CartServiceImpl implements CartService {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private ProductFeignService productFeignService;

    @Resource
    private ThreadPoolExecutor executor;


    /**
     * 获取整个购物车，也就是在商品页，点击购物车那块
     * 查询购物车列表：判断是否登录
     * <p>
     * 否：直接根据 user-key 查询 redis 中数据并展示
     * <p>
     * 是：已登录，则需要先根据 user-key 查询 redis 是否有数据。
     * -     有：需要提交到后台添加到 redis，合并数据（合并临时购物车和用户购物车），而后根据userid查询。
     * -     否：直接去后台根据userid查询 redis，而后返回。
     */
    @Override
    public Cart getCart() {
        UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();

        Cart cart = new Cart();

        String liCacheKey = CartConstant.CACHE_PREFIX + userInfoTo.getUserKey();

        if (userInfoTo.getUserId() == null) { //如果未登录，直接从redis中查询临时购物车
            //直接获取临时购物车中的数据
            List<CartItemVO> cartItemVos = getCartItemVos(liCacheKey);
            cart.setCartItemVOList(cartItemVos);
        } else { //如果已经登录，先根据user-key查询临时购物车，然后合并两个购物车(如果临时购物车有数据就合并),最终查询用户购物车(包含合并后的临时购物车)
            //1.查询临时购物车
            List<CartItemVO> lsCartItemVos = getCartItemVos(liCacheKey);

            //2.合并临时购物车和用户购物车
            if (lsCartItemVos.size() > 0) {
                //将临时购物车的购物项逐个添加到用户购物车中
                lsCartItemVos.forEach(lsCartItemVO -> {
                    try {
                        addCartItem(lsCartItemVO.getSkuId(), lsCartItemVO.getCount());
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                });
            }

            //3.合并购物车之后，清空临时购物车
            clearCart(liCacheKey);

            //4.查询合并后的用户购物车(包含之前的临时购物车和用户购物车)
            String userCacheKey = CartConstant.CACHE_PREFIX + userInfoTo.getUserId();
            List<CartItemVO> userCartItemVos = getCartItemVos(userCacheKey);

            cart.setCartItemVOList(userCartItemVos);
        }

        return cart;
    }


    /**
     * 获取指定用户(注册用户和临时用户)购物车中的所有购物项
     *
     * @param cacheKey redis中的购物车的key
     */
    public List<CartItemVO> getCartItemVos(String cacheKey) {
        //第一String是gulimall:cart:6，第二个String是skuId，第三个Object是CartItemVO
        BoundHashOperations<String, String, Object> hashOps = redisTemplate.boundHashOps(cacheKey);

        Collection<Object> cartItemVOS = Objects.requireNonNull(hashOps.entries()).values();

        ObjectMapper mapper = new ObjectMapper();
        String s = null;
        List<CartItemVO> itemVOS = null;
        try {
            s = mapper.writeValueAsString(cartItemVOS);
            itemVOS = mapper.readValue(s, new TypeReference<List<CartItemVO>>() {
            });
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
        return itemVOS;
    }


    /**
     * 清空购物车
     *
     * @param cacheKey redis中购物车的key
     */
    public void clearCart(String cacheKey) {
        redisTemplate.delete(cacheKey);
    }


    /**
     * 获取用户(临时用户和在线用户)购物车
     *
     * @return
     */
    private BoundHashOperations<String, Object, Object> getCartHashOps() {
        UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();

        String cacheKey = "";

        BoundHashOperations<String, Object, Object> hashOps = null;

        //1.未登录，获取临时购物车
        if (userInfoTo.getUserId() == null) {
            cacheKey = CartConstant.CACHE_PREFIX + userInfoTo.getUserKey();
            hashOps = redisTemplate.boundHashOps(cacheKey);
        } else {//2.已登录，获取用户购物车
            cacheKey = CartConstant.CACHE_PREFIX + userInfoTo.getUserId();
            hashOps = redisTemplate.boundHashOps(cacheKey);
        }


        return hashOps;
    }


    /**
     * 给购物车里面添加商品(购物项)
     * 如果远程查询比较慢，比如方法当中有好几个远程查询，都要好几秒以上，等整个方法返回就需要很久，这块你是怎么处理的?
     * 1、为了提交远程查询的效率，可以使用线程池的方式，异步进行请求
     * 2、要做的操作就是将所有的线程全部放到自己手写的线程池里面
     * 3、每一个服务都需要配置一个自己的线程池
     * 4、完全使用线程池来控制所有的请求
     *
     * @param skuId 商品id
     * @param num   数量
     */
    @Override
    public CartItemVO addCartItem(Long skuId, Long num) throws JsonProcessingException, ExecutionException, InterruptedException {
        ThreadLocal<UserInfoTo> threadLocal = CartInterceptor.threadLocal;
        UserInfoTo userInfoTo = threadLocal.get();

        ObjectMapper mapper = new ObjectMapper();

        //1.获取到要操作的购物车
        BoundHashOperations<String, Object, Object> cartHashOps = getCartHashOps();

        Object o = cartHashOps.get(skuId.toString());
        String jsonString = mapper.writeValueAsString(o);

        if (o == null) {//如果购物车中没有该该商品
            CartItemVO cartItemVO = new CartItemVO();

            //2.异步任务1：远程调用商品服务获取sku信息
            CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
                R r = productFeignService.info(skuId);
                SkuInfoVO skuInfo = r.get("skuInfo", new TypeReference<SkuInfoVO>() {
                });
                cartItemVO.setSkuId(skuId);
                cartItemVO.setTitle(skuInfo.getSkuTitle());
                cartItemVO.setDefaultImg(skuInfo.getSkuDefaultImg());
                cartItemVO.setPrice(skuInfo.getPrice());
                cartItemVO.setCount(num);
            }, executor);

            //3.异步任务2：远程查询skuAttr信息
            //远程查出当前商品的sku的销售属性组合信息，需要在购物车里面进行展示
            CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
                R r = productFeignService.listBySkuId(skuId);
                List<SkuSaleAttrValueVO> attrValueVOS = r.get("skuSaleAttrList", new TypeReference<List<SkuSaleAttrValueVO>>() {
                });
                List<String> attrValues = attrValueVOS.stream()
                        .map(skuSaleAttrValueVO -> {
                            String attrName = skuSaleAttrValueVO.getAttrName();
                            String attrValue = skuSaleAttrValueVO.getAttrValue();

                            //数据格式：   颜色：星河银   版本：8G+256G
                            return attrName + "：" + attrValue;
                        })
                        .collect(Collectors.toList());
                cartItemVO.setSkuAttr(attrValues);
            }, executor);


            CompletableFuture.allOf(future1, future2).get();
            //4.当两个异步任务都执行完成之后执行
            //String cartItemVOString = mapper.writeValueAsString(cartItemVO);
            //把购物项的数据保存redis中
            cartHashOps.put(skuId.toString(), cartItemVO);
            if (userInfoTo.getUserId()==null){//用户未登录就给购物车设置一个过期时间，和临时用户的cookie过期时间保持一致
                cartHashOps.expire(CartConstant.TEMP_USER_COOKIE_TIMEOUT, TimeUnit.SECONDS);//设置临时购物车的过期时间
            }

            return cartItemVO;

        } else {//如果购物车中已经含有该商品，则直接改变商品数量就行
            CartItemVO itemVO = mapper.readValue(jsonString, CartItemVO.class);
            itemVO.setCount(itemVO.getCount() + num);
            //更新redis
            //cartHashOps.put(skuId.toString(), mapper.writeValueAsString(cartItemVO));
            cartHashOps.put(skuId.toString(), itemVO);
            return itemVO;
        }
    }


    /**
     * 根据skuId从redis中获取购物项CartItemVO
     *
     * @param skuId
     * @return
     */
    @Override
    public CartItemVO getCartItem(Long skuId) throws JsonProcessingException {
        //1.获取当前在线用户的购物车
        BoundHashOperations<String, Object, Object> cartHashOps = getCartHashOps();

        //2.根据skuId从购物车中取出购物项
        Object o = Objects.requireNonNull(cartHashOps.get(skuId.toString()));
        ObjectMapper mapper = new ObjectMapper();
        String s = mapper.writeValueAsString(o);


        //3.将json字符串转换为java对象并返回
        return mapper.readValue(s, CartItemVO.class);
    }


    @Override
    public void checkItem(Long skuId, Integer check) throws JsonProcessingException {
        BoundHashOperations<String, Object, Object> cartOps = getCartHashOps();//获取哪个用户的购物车
        //先看看操作的是哪个购物车
        CartItemVO cartItem = getCartItem(skuId);//得到相应的购物车当中的购物项
        cartItem.setCheck(check == 1); //设置最新的状态信息
        //重新序列化存到redis
        //String s = new ObjectMapper().writeValueAsString(cartItem);
        cartOps.put(skuId.toString(), cartItem);//key就是当前购物项的id，值就是修改购物项状态后的新值

    }

    /**
     * 修改购物车当中购物项的数量
     *
     * @param skuId
     * @param num
     */
    @Override
    public void changeItemCount(Long skuId, Long num) throws JsonProcessingException {
        //先拿到购物项原本的信息
        CartItemVO cartItem = getCartItem(skuId);
        cartItem.setCount(num);

        //再拿到购物车，重新保存到redis，
        BoundHashOperations<String, Object, Object> cartOps = getCartHashOps();//根据当前登录状态来获取到购物车
        //获取到以后，改变当前商品的id作为key和改好后的数量作为值，再保存到redis当中
        cartOps.put(skuId.toString(), cartItem);
    }

    /**
     * 在购物车当中根据商品id来删除指定的购物项
     * 从redis中删除指定id的购物项
     *
     * @param skuId
     */
    @Override
    public void deleteItem(Long skuId) {
        //拿到当前购物车的操作
        BoundHashOperations<String, Object, Object> cartOps = getCartHashOps();
        cartOps.delete(skuId.toString());
    }
}
